package com.exadel;

import java.util.NoSuchElementException;
import java.util.TreeMap;

public class RealMoneyKeeper implements Calculation {
	protected TreeMap<Integer, Integer> money = new TreeMap<Integer, Integer>();
	protected int pointerNominal;
	protected int last;
	protected RealMoneyKeeper wallet;
	protected int countOfRollback;
	protected int firstCommitNomianl;
	protected int countOfMoney = 0;
	protected StringBuilder keepMoney = new StringBuilder();
	
	private int moneyLastKey;
	
	public String Instance(){
		return "RealMoneyKeeper";
	}
	private  void init() throws NoNominalException{
		
		wallet = new RealMoneyKeeper();
		if( money == null)
			throw new NoNominalException();
		this.pointerNominal = getNominalls().lastKey();
		this.last = pointerNominal;
		//wallet.clear();
		countOfRollback = 0;
		firstCommitNomianl = getNominalls().lastKey()+1;
	}
    private  void put(int key){
    	int count = 0;
    		try{
    			count = getNominalls().get(key);
    			getNominalls().put(key, ++count);
    		   }
    		catch(NullPointerException e){
    			getNominalls().put(key, 1);
    	}
    }
    private  int tos(int key) throws ImpossibleToGiveMoneyException{
    	for(;getNominalls().firstKey() > 0;){
    		int count = getNominalls().get(key);
    		if(count > 0){
    			return key;
    		}
    		else if(key == this.money.lastKey()){
    			this.money.clear();
    			throw new ImpossibleToGiveMoneyException();
    		}
    		else if(key != this.money.lastKey())
    			key = getNominalls().higherKey(key);
    		}
    	
    	return key;
    }
    private  int pop(int key) {
    	int count = getNominalls().get(key);
    	if(count > 0){
    		getNominalls().put(key, --count);
    		return key;
    	}
    	else return 0;
    }
    private  int commit(int money){
		if(firstCommitNomianl == this.getNominalls().lastKey()+1){
			firstCommitNomianl = pointerNominal;
			last = firstCommitNomianl;
		}
		else{
			countOfRollback = 0;
		}
		pointerNominal = this.lowtos(pointerNominal);
		wallet.put(pointerNominal);
		money -= pointerNominal;
		pop(pointerNominal);
		return money;
	}
    private  int rollback(int money) throws ImpossibleToGiveMoneyException{
		if(wallet.getNominalls().size() <= 0){
			countOfRollback = 0;
			 if(firstCommitNomianl == this.money.firstKey()){
				 wallet.money.clear();
					throw new ImpossibleToGiveMoneyException();
			}
			firstCommitNomianl = this.getNominalls().lowerKey(firstCommitNomianl);
		}
		else if(last > this.getNominalls().firstKey()){
			//
			int temp = wallet.tos(wallet.getNominalls().firstKey());
			money += temp;
			put(temp);
			wallet.pop(temp);
			//
			last = this.getNominalls().lowerKey(last);
			
			pointerNominal = last;
			return money; 
			}
		else if(last <= this.getNominalls().firstKey() & pointerNominal <= this.getNominalls().firstKey()){
			countOfRollback++;
			for(int i = 0; i<=countOfRollback;i++){
				try{
					int temp = wallet.tos(last);
					money += temp;
					put(temp);
					wallet.pop(temp);
					
					
				}
				catch(NoSuchElementException ex){
				}
			}
			firstCommitNomianl = this.getNominalls().lowerKey(firstCommitNomianl);
			last = firstCommitNomianl;
			pointerNominal = last;
			countOfRollback++;
		}
		else {
					throw new NoSuchElementException();
				}
			return money;
	}
    private  void Prev(){
		int next = lower(pointerNominal);
		if(getNominalls().lowerKey(pointerNominal) != 0 & next != 0){
			pointerNominal = next;
		}
    }
    
    public   int lower(int key){
   	
    		key = getNominalls().lowerKey(key);
    		for(;key >= getNominalls().firstKey();){
    			if(getNominalls().get(key) > 0)
    				return key;
    			else
    				key = getNominalls().lowerKey(key);
    		}
   		return key;
    }
    public   void SetCountOfMoney(int countOfMoney) throws InvalidNumberException, NoNominalException{
         //validation numbers
     	keepMoney.append("");
         if (countOfMoney > 0){
             this.countOfMoney = countOfMoney;
             init();
         }
         else{
         	throw new InvalidNumberException();
         }
     }
    public   String GetOdd(){
         return wallet.getNominalls().toString();
     }
    public   int lowtos(int key){
    	
    	int count = getNominalls().get(key);
    	
		
    	while(count == 0){
    		 if(getNominalls().get(getNominalls().firstKey())==0 & key == getNominalls().firstKey()){
					return 0;
    		}
    		else if(key != getNominalls().firstKey()) {
    			key =  getNominalls().lowerKey(key);
    			count = getNominalls().get(key);
    			
    		}
    		
    	}
    	 return key;
    }
	public   RealMoneyKeeper getWallet(){
		return wallet;
	}
	public   TreeMap<Integer, Integer> getInstance(){
		return getNominalls();
	}
	public   int size(){
		return this.getNominalls().size();
	}
	public   boolean isPrev(){
		if(getNominalls().lowerEntry(pointerNominal) != null)
			return true;
		else if(getNominalls().lowerEntry(last) != null)
		return false;
		else return false;
	}
	public   void setNominal(int... nominal) throws InvalidNumberException{
		
			for(int k : nominal)
			{
				if(k > 0)
					getNominalls().put(k,     0);
				else
					throw new InvalidNumberException();
			}
				
			moneyLastKey = getNominalls().lastKey();
		last = moneyLastKey; 
	}
	public   void setCount(int key,int count) throws InvalidNumberException{
		
			if(key>0 & count > 0)
				getNominalls().put(key,     count);
			else
				throw new InvalidNumberException();
	}
	public   TreeMap<Integer, Integer> getCash() {
		return getNominalls();
	}
	public   void ComputeOdd() throws ImpossibleToGiveMoneyException {
		for(;countOfMoney > 0;){
	         // if have money, put money into the wallet
	          	if(countOfMoney - pointerNominal >= 0){
	         	 		countOfMoney = commit(countOfMoney);
	         	 	}
	          	// if don't have money then watch less cash
	         	 	else if(countOfMoney < pointerNominal  & isPrev()){
	      		 Prev();
	         	 	}
	          	//if have a money but don't have cash, should be given cash from wallet
	         	 	else if( countOfMoney > 0 & !isPrev()){
								countOfMoney = rollback(countOfMoney);
	      	 	}
	          	// all money exchanged
	          	else if(countOfMoney == 0)
	         	 		return;
	        }
	}
	public   TreeMap<Integer, Integer> getNominalls() {
		return money;
	}
	public void PrintNominalls(){
		System.out.println("realMoneyKeeper = " + money);
	}
	public   void setMoney(TreeMap<Integer, Integer> money) {
		this.money = money;
	}
	public void deleteNominal(int index){
			money.remove(index);
	}
	public void PrintOdd(){
		System.out.println(this.GetOdd());
	}
}
